package com.cgs.query.server.service

import com.cgs.query.domain.ExecutionObj
import com.cgs.query.domain.MappedStatement
import com.cgs.query.domain.QuerySource
import com.cgs.query.event.QueryProjectEvent
import com.cgs.query.exception.QueryException
import com.cgs.query.executor.Executor
import com.cgs.query.handler.DefineFindHandler
import com.cgs.query.handler.SourceFindHandler
import com.cgs.query.service.DefineSource
import com.cgs.query.service.IQueryService
import com.cgs.query.service.QuerySourceMap
import com.sucsoft.kotlin.annotation.AllOpen
import mu.KotlinLogging
import org.springframework.context.ApplicationEventPublisher
import org.springframework.context.ApplicationEventPublisherAware

private val logger = KotlinLogging.logger { }

/***
 * 公共池,不需要权限进行执行
 */
const val publicSqlPoolName = "PUBLIC_SQL_POOL"

@AllOpen
open class ServerQueryService(private val executor: Executor,
                              private val defineFindHandler: DefineFindHandler,
                              private val sourceFindHandler: SourceFindHandler) : IQueryService, ApplicationEventPublisherAware {

    private lateinit var applicationEventPublisher: ApplicationEventPublisher

    /**
     * 远程调用
     */
    override fun execute(projectName: String, name: String, param: Map<String, Any?>?, timeout: Int?): Any? {
        logger.debug { "执行查询定义:[$projectName : $name],参数[$param]" }
        return localExecute(projectName, name, param, null, timeout)
    }

    /**
     * 服务端公共sql池进行查询
     */
    open fun serverPageExecute(id: String, sourceId: String, param: Map<String, Any?>?, limit: Int, timeout: Int?): Any? {
        val executionObj = defineFindHandler.findExecutionObjById(id)
                ?: throw QueryException("找不到Sql定义:$id")
        val querySource = QuerySource(sourceId)
        return executor.execute(MappedStatement(querySource, executionObj).apply {
            this.limit = limit
            this.projectName = publicSqlPoolName
        }, param, timeout)
    }

    /**
     * 在项目进行执行
     */
    open fun projectPageExecute(projectName: String, callName: String?, id: String, param: Map<String, Any?>?, limit: Int, timeout: Int): Any? {
        val executionObj = logThenThrow(defineFindHandler.findExecutionObjById(id), "找不到Sql定义:$id", projectName, callName)
        val querySource = logThenThrow(sourceFindHandler.findSourceByProjectName(projectName), "找不到数据源$projectName", projectName, callName)
        return baseExecute(MappedStatement(querySource, executionObj).apply {
            this.limit = limit
            this.projectName = projectName
            this.wholeName = callName
        }, param, timeout)
    }

    /**
     * 一个完整的执行流程
     * wholeName.defineName
     */
    private fun localExecute(projectName: String, wholeName: String, param: Map<String, Any?>?, limit: Int?, timeout: Int?): Any? {
        val executionObj = logThenThrow(findByNameAndProjectName(wholeName, projectName), "找不到Sql定义:$projectName.$wholeName", projectName, wholeName)

        val sourceId = logThenThrow(sourceFindHandler.findSourceIdByProjectNameAndWholeName(projectName, wholeName)
                , "找不到数据源:$projectName.$wholeName", projectName, wholeName)
        val querySource = QuerySource().apply { id = sourceId }
        return baseExecute(MappedStatement(projectName, wholeName, querySource, executionObj).apply { this.limit = limit }, param, timeout)
    }

    private fun baseExecute(mappedStatement: MappedStatement, param: Map<String, Any?>?, timeout: Int?): Any? {
        try {
            return executor.execute(mappedStatement, param, timeout)
        } catch (ex: Exception) {
            applicationEventPublisher.publishEvent(QueryProjectEvent(mappedStatement.projectName
                    ?: publicSqlPoolName, QueryProjectEvent.Type.EXE_ERROR).apply {
                this.e = ex
                this.callName = mappedStatement.wholeName
            })
            throw ex
        }
    }

    private fun findByNameAndProjectName(name: String, projectName: String): ExecutionObj? {
        return defineFindHandler.findExecutionObj(projectName, name)
    }

    override fun findDefineSourceByWholeNameAndProjectName(name: String, projectName: String): DefineSource {
        return DefineSource(name, projectName, null, findByNameAndProjectName(name, projectName), sourceFindHandler.findSourceByProjectNameAndWholeName(projectName, name))
    }

    override fun findSourceByProjectNames(names: List<String>): QuerySourceMap {
        return QuerySourceMap(names.associateBy({ it -> it }, { it -> sourceFindHandler.findSourceByProjectName(it) }))
    }

    override fun setApplicationEventPublisher(applicationEventPublisher: ApplicationEventPublisher?) {
        this.applicationEventPublisher = applicationEventPublisher!!
    }

    private fun <T> logThenThrow(t: T?, msg: String, projectName: String, callName: String?): T {
        return if (t == null) {
            val ex = QueryException(msg)
            applicationEventPublisher.publishEvent(QueryProjectEvent(projectName, QueryProjectEvent.Type.EXE_ERROR).apply {
                this.e = ex
                this.callName = callName
            })
            throw ex
        } else {
            t
        }
    }
}

